
#include "stdafx.h"

#include <string>

#include <vcclr.h>

#include "ImageCodecPlugin.h"

using namespace std;
using namespace Derm;

using namespace System::Runtime::InteropServices;

/*===========================================================================*/
/* LibRawLite Interoperability - Stream                                      */
/*===========================================================================*/

class RawLiteStream : public LibRaw_abstract_datastream
{
public:

	RawLiteStream(Stream ^stream) :
		Handle(stream)
	{
		
	}

	virtual ~RawLiteStream()
	{

	}

    virtual int valid()
	{ 
		return (true);
	}

    virtual int read(void *buffer, size_t size, size_t count)
	{
		// Check for substream operation
		if (substream != NULL)
			return substream->read(buffer, size, count);

		cli::array<unsigned char> ^mbuffer = gcnew cli::array<unsigned char>(size * count);
		int bytes;

		// Read from file
		bytes = Handle->Read(mbuffer, 0, size * count);
		// Copy to buffer
		Marshal::Copy(mbuffer, 0, (IntPtr)buffer, bytes);

		return (bytes);
	}

    virtual int eof()
	{
		// Check for substream operation
        if (substream != NULL)
			return (substream->eof());

		return (Handle->Position >= Handle->Length);
    }

    virtual int seek(INT64 offset, int origin)
	{
		// Check for substream operation
        if (substream != NULL)
			return (substream->seek(offset, origin));

		switch (origin) {
			case SEEK_SET:
				Handle->Seek(offset, SeekOrigin::Begin);
				break;
			case SEEK_CUR:
				Handle->Seek(offset, SeekOrigin::Current);
				break;
			case SEEK_END:
				Handle->Seek(offset, SeekOrigin::End);
				break;
		}

		return (Handle->Position);
	}

    virtual INT64 tell()
	{ 
		// Check for substream operation
		if (substream != NULL)
			return (substream->tell());

        return (Handle->Position);
    }

    virtual int get_char()
	{
		// Check for substream operation
		if (substream != NULL)
			return (substream->get_char());

		return (Handle->ReadByte());
	}

	virtual char* gets(char *buffer, int length)
	{
		// Check for substream operation
		if (substream != NULL)
			return (substream->gets(buffer, length));

		// Reset string buffer
		memset(buffer, 0, length);
		// Fill up string buffer
		for (int i = 0; i < length; i++) {
			/* Read byte per byte */
			if (read(&buffer[i], 1, 1) == 0)
				return (NULL);
			/* Check for line ending ('\n') */
			if(buffer[i] == 0x0A)
				break;
		}

		return (buffer);
	}

	virtual int scanf_one(const char *fmt, void *val)
	{
		const int BUFFER_LIMIT = 64;

		// Check for substream operation
		if (substream != NULL)
			return (substream->scanf_one(fmt, val));

		char buffer[BUFFER_LIMIT];
		int bufferSize = (Handle->Length - Handle->Position > BUFFER_LIMIT) ? BUFFER_LIMIT : Handle->Length - Handle->Position;
		INT64 pos = tell();

		// Read buffer
		bufferSize = read(buffer, 1, bufferSize);
		// Scan buffer
		int ret = sscanf(buffer, fmt, val);

		if(ret > 0) {
			int limit = 0;

			while(pos < Handle->Length) {
				pos++; limit++;

				if (buffer[pos] == 0 || buffer[pos] == ' ' || buffer[pos]=='\t' || buffer[pos]=='\n' || limit > bufferSize)
					break;
			}
		}
		
		seek(pos, SEEK_SET);

		return (ret);
	}

private:

	gcroot<Stream^> Handle;

}; /* class RawLiteStream */

/*===========================================================================*/
/* RawLiteImageCodecPlugin                                                   */
/*===========================================================================*/

bool RawLiteImageCodecPlugin::CheckAvailability()
{
	return (true);
}

ImageInfo ^RawLiteImageCodecPlugin::QueryImageInfo(Stream ^fs, ImageCodec::CodecCriteria ^criteria)
{
	LibRaw rProcessor;
	RawLiteStream stream(fs);
	int err;

	// Open raw image
	if((err = rProcessor.open_datastream(&stream)) != LIBRAW_SUCCESS)
		throw gcnew InvalidOperationException("failed to open input stream, error code " + gcnew String(libraw_strerror(err)));

	return (QueryImageInfo(rProcessor));
}

ImageInfo ^RawLiteImageCodecPlugin::QueryImageInfo(LibRaw &rProcessor)
{
	__try {
		ImageInfo ^info = gcnew ImageInfo();

		info->ContainerFormat = ImageFormat::Raw;
		info->Width = rProcessor.imgdata.sizes.width;
		info->Height = rProcessor.imgdata.sizes.height;
		info->PixelType = Pixel::Type::RGB48;

		if (strlen(rProcessor.imgdata.other.desc) > 0)
			info->SetTag(ImageInfo::TagComment, gcnew String(rProcessor.imgdata.other.desc));
		if (strlen(rProcessor.imgdata.other.artist) > 0)
			info->SetTag(ImageInfo::TagAcquisitionArtist, gcnew String(rProcessor.imgdata.other.artist));
		if ((strlen(rProcessor.imgdata.idata.make) > 0) || (strlen(rProcessor.imgdata.idata.model) > 0))
			info->SetTag(ImageInfo::TagAcquisitionDevice, gcnew String(rProcessor.imgdata.idata.make) + " " + gcnew String(rProcessor.imgdata.idata.model));

		return (info);
	} __finally {
		rProcessor.recycle();
	}
}

Image ^RawLiteImageCodecPlugin::Load(Stream ^fs, ImageCodec::CodecCriteria ^criteria)
{
	LibRaw rProcessor;
	RawLiteStream stream(fs);
	int err;

	// Open raw image
	if((err = rProcessor.open_datastream(&stream)) != LIBRAW_SUCCESS)
		throw gcnew InvalidOperationException("failed to open input stream, error code " + gcnew String(libraw_strerror(err)));

	return (Load(rProcessor, criteria));
}

Image ^RawLiteImageCodecPlugin::Load(LibRaw &rProcessor, ImageCodec::CodecCriteria ^criteria)
{
	Image ^image;

	__try {
		Pixel::Type pType = Pixel::Type::RGB24;
		int err;

		// Decoder setup: bits per pixel
		rProcessor.imgdata.params.output_bps = Pixel::GetComponentBits(pType, 0);
		// Decoder setup: gamma correction and slope curve
		if        (pType == Pixel::Type::RGB48) {
			// set -g 1 1 for linear curve
			rProcessor.imgdata.params.gamm[0] = 1;
			rProcessor.imgdata.params.gamm[1] = 1;
		} else if (pType == Pixel::Type::RGB24) {
			// by default settings for rec. BT.709 are used: power 2.222 (i.e. gamm[0]=1/2.222) and slope 4.5
			rProcessor.imgdata.params.gamm[0] = 1/2.222;
			rProcessor.imgdata.params.gamm[1] = 4.5;
		}
		// Decoder setup: use camera white balance, if possible (otherwise, fallback to auto_wb)
		rProcessor.imgdata.params.use_camera_wb = 1;
		// Decoder setup: use automatic white balance obtained after averaging over the entire image
		rProcessor.imgdata.params.use_auto_wb = 1;
		// Decoder setup: adaptive homogeneity-directed demosaicing algorithm (AHD)
		rProcessor.imgdata.params.user_qual = 3;
		// Decoder setup: RAW data filtration mode during data unpacking and postprocessing
		rProcessor.imgdata.params.filtering_mode = LIBRAW_FILTERING_AUTOMATIC;

		// Read pixel data
		if((err = rProcessor.unpack()) != LIBRAW_SUCCESS)
			throw gcnew InvalidOperationException("failed to unpack data, error code " + gcnew String(libraw_strerror(err)));
		// Process pixel data
		if((err = rProcessor.dcraw_process()) != LIBRAW_SUCCESS)
			throw gcnew InvalidOperationException("failed to process data, error code " + gcnew String(libraw_strerror(err)));

		// Transpose processed image
		int rawwidth = 0, rawheight = 0, rawcolors = 0, rawbps = 0;

		rProcessor.get_mem_image_format(&rawwidth, &rawheight, &rawcolors, &rawbps);

		if (rawcolors != (int)Pixel::GetComponentsCount(pType))
			throw gcnew InvalidOperationException("color components count mismatch");
		if (rawbps != (int)Pixel::GetComponentBits(pType, 0))
			throw gcnew InvalidOperationException("color components bits mismatch");

		// Create image
		image = Image::CreateImage(rawwidth, rawheight, pType);

		// Copy data to managed buffer
		System::Object ^imageData = image->GetPixelData();
		GCHandle ^hImageData = GCHandle::Alloc(imageData, GCHandleType::Pinned);

		__try {
			char *imageDataPtr = reinterpret_cast<char*>(hImageData->AddrOfPinnedObject().ToPointer());

			rProcessor.copy_mem_image(imageDataPtr, image->Stride, 0);
		} __finally {
			hImageData->Free();
		}

		// libRaw specify image lines from top to bottom
		image->FlipVertically();

		return (image);
	} __finally {
		rProcessor.recycle();
	}
}

void RawLiteImageCodecPlugin::Save(Stream ^fs, Image ^image, ImageFormat format, ImageCodec::CodecCriteria ^criteria)
{
	throw gcnew NotImplementedException();
}
